<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Chrome DevTools Protocol Viewer</title>

  <base href="/" />

  <meta name="theme-color" content="#303F9F">

  <script src="bower_components/webcomponentsjs/webcomponents-loader.js"></script>
  <script src="bower_components/web-animations-js/web-animations-next.min.js"></script>

  <link rel="import" href="index-imports.html" />

  <link href="styles/protocol.css" rel="stylesheet">

  <!-- Start Single Page Apps for GitHub Pages -->
  <script type="text/javascript">
    // Single Page Apps for GitHub Pages
    // https://github.com/rafrex/spa-github-pages
    // Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
    // ----------------------------------------------------------------------
    // This script checks to see if a redirect is present in the query string
    // and converts it back into the correct url and adds it to the
    // browser's history using window.history.replaceState(...),
    // which won't cause the browser to attempt to load the new url.
    // When the single page app is loaded further down in this file,
    // the correct url will be waiting in the browser's history for
    // the single page app to route accordingly.
    (function(l) {
      if (l.search) {
        var q = {};
        l.search.slice(1).split('&').forEach(function(v) {
          var a = v.split('=');
          q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&');
        });
        if (q.p !== undefined) {
          window.history.replaceState(null, null,
            l.pathname.slice(0, -1) + (q.p || '') +
            (q.q ? ('?' + q.q) : '') +
            l.hash
          );
        }
      }
    }(window.location))
  </script>
  <!-- End Single Page Apps for GitHub Pages -->
</head>
<body unresolved>
  <custom-style>
    <style is="custom-style" include="custom-paper-material-styles">
      [unresolved] > * {
        display: none !important;
      }
    </style>
  </custom-style>
  <iron-location id="location"></iron-location>

  <app-drawer-layout id="paperDrawerPanel">
    <app-drawer slot="drawer" id="drawer">
      <div id="drawerContainer">
        <paper-dropdown-menu id="versions" label="Version">
          <paper-listbox slot="dropdown-content" attr-for-selected="name">
            <a href="1-2" name="1-2">
              <paper-item>stable (1.2)</paper-item>
            </a>
            <a href="1-3" name="1-3">
              <paper-item>stable RC (1.3)</paper-item>
            </a>
            <a href="tot" name="tot">
              <paper-item>latest (tip-of-tree)</paper-item>
            </a>
            <a href="v8" name="v8">
              <paper-item>v8-inspector (node)</paper-item>
            </a>
            <a href="" name="" class="homelink">
              <paper-icon-button icon="home"></paper-icon-button><span>Home</span>
            </a>
          </paper-listbox>
        </paper-dropdown-menu>
        <div id="drawerToolbar" class="paper-font-title">Domains</div>
        <iron-selector id="domainContainer" attr-for-selected="data-title">
          <a data-title="Accessibility" class="tot">Accessibility</a>
          <a data-title="Animation" class="tot">Animation</a>
          <a data-title="ApplicationCache" class="tot">ApplicationCache</a>
          <a data-title="Audits" class="tot">Audits</a>
          <a data-title="Browser" class="1-3 tot">Browser</a>
          <a data-title="CacheStorage" class="tot">CacheStorage</a>
          <a data-title="Console" class="tot v8">Console</a>
          <a data-title="CSS" class="tot">CSS</a>
          <a data-title="Database" class="tot">Database</a>
          <a data-title="Debugger" class="1-2 1-3 tot v8">Debugger</a>
          <a data-title="DeviceOrientation" class="tot">DeviceOrientation</a>
          <a data-title="DOM" class="1-2 1-3 tot">DOM</a>
          <a data-title="DOMDebugger" class="1-2 1-3 tot">DOMDebugger</a>
          <a data-title="DOMSnapshot" class="tot">DOMSnapshot</a>
          <a data-title="DOMStorage" class="tot">DOMStorage</a>
          <a data-title="Emulation" class="1-2 1-3 tot">Emulation</a>
          <a data-title="HeadlessExperimental" class="tot">HeadlessExperimental</a>
          <a data-title="HeapProfiler" class="tot v8">HeapProfiler</a>
          <a data-title="IndexedDB" class="tot">IndexedDB</a>
          <a data-title="Input" class="1-2 1-3 tot">Input</a>
          <a data-title="Inspector" class="tot">Inspector</a>
          <a data-title="IO" class="1-3 tot">IO</a>
          <a data-title="LayerTree" class="tot">LayerTree</a>
          <a data-title="Log" class="1-3 tot">Log</a>
          <a data-title="Memory" class="tot">Memory</a>
          <a data-title="Network" class="1-2 1-3 tot">Network</a>
          <a data-title="Overlay" class="tot">Overlay</a>
          <a data-title="Page" class="1-2 1-3 tot">Page</a>
          <a data-title="Performance" class="1-3 tot">Performance</a>
          <a data-title="Profiler" class="1-2 1-3 tot v8">Profiler</a>
          <a data-title="Runtime" class="1-2 1-3 tot v8">Runtime</a>
          <a data-title="Schema" class="1-2 1-3 tot v8">Schema</a>
          <a data-title="Security" class="1-3 tot">Security</a>
          <a data-title="ServiceWorker" class="tot">ServiceWorker</a>
          <a data-title="Storage" class="tot">Storage</a>
          <a data-title="SystemInfo" class="tot">SystemInfo</a>
          <a data-title="Target" class="tot">Target</a>
          <a data-title="Tethering" class="1-3 tot">Tethering</a>
          <a data-title="Tracing" class="tot">Tracing</a>
        </iron-selector>
      </div>
    </app-drawer>

    <div class="header-layout">
      <app-toolbar id="mainToolbar">
        <paper-icon-button id="paperToggle"
            icon="menu" drawer-toggle aria-label="Open drawer menu"></paper-icon-button>
        <div main-title class="top title">
          Chrome DevTools Protocol Viewer
        </div>
        <cr-search-control id="searchButton"></cr-search-control>
      </app-toolbar>

      <main class="content">
        <iron-pages id="main-selector" attr-for-selected="data-url" fallback-selection="404">
          <section data-url="404">
            Oops you hit a 404!
          </section>
          <cr-search-menu data-url="search" id="searchMenu"></cr-search-menu>
          <section data-url="domain-summary" class="paper-material" elevation="1">
            <a href="https://github.com/ChromeDevTools/debugger-protocol-viewer" class="gh-badge"><img
              style=""
              src="https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67"
              alt="Fork me on GitHub"
              data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"></a>
            <div id="domain-summary"></div>
          </section>
          <cr-domain data-url="domain"></cr-domain>
          <section data-url="">
            <div class="paper-material home-page-content" elevation="1">
              <a href="https://github.com/ChromeDevTools/debugger-protocol-viewer" class="gh-badge"><img
                style=""
                src="https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67"
                alt="Fork me on GitHub"
                data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"></a>
              <p>The <b>Chrome DevTools Protocol</b> allows for tools to instrument, inspect, debug and profile Chromium, Chrome and other Blink-based browsers.
              Many existing projects <a href="https://developer.chrome.com/devtools/docs/debugging-clients">currently use</a> the protocol.
              The <a href="https://developers.google.com/web/tools/chrome-devtools/">Chrome DevTools</a> uses this protocol and the team maintains its API.

              <p>Instrumentation is divided into a number of domains (DOM, Debugger, Network
              etc.). Each domain defines a number of commands it supports and events it
              generates. Both commands and events are serialized JSON objects of a fixed
              structure. You can either <a href="https://developer.chrome.com/devtools/docs/debugger-protocol">debug
              over the wire</a> using the raw messages as they are described in the corresponding domain
              documentation, or use <a href="https://developer.chrome.com/devtools/docs/debugger-protocol#extension">extension
              JavaScript API.</a></p>


              <h3>Protocol API Docs</h3>
              <h4><a href="tot/">The latest (tip-of-tree) protocol (tot)</a></h4>
              <p> It <a href="https://chromium.googlesource.com/chromium/src/+log/master/third_party/blink/renderer/core/inspector/browser_protocol.json">changes</a>
              <a href="https://chromium.googlesource.com/v8/v8.git/+log/master/src/inspector/js_protocol.json"></a>frequently</a> and can break at any time. However it captures the full capabilities of the Protocol, whereas the stable release is a subset. There is no backwards compatibility support guaranteed for the capabilities it introduces.</p>

              <h4><a href="v8/">v8-inspector protocol (v8)</a></h4>
              <p>It is available in <a href="https://nodejs.org/en/blog/release/v6.3.0/">node 6.3+</a> and enables <a href="https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27">debugging & profiling</a> of Node.js apps.</p>

              <h4><a href="1-2/">stable 1.2 protocol (1-2)</a></h4>
              <p>The stable release of the protocol, tagged at Chrome 54. It includes a smaller subset of the complete protocol compatibilities.

              <h4><a href="1-3/">stable 1.3 protocol (1-3)</a></h4>
              <p>The stable release of the protocol, tagged at Chrome 64. It includes a smaller subset of the complete protocol compatibilities.

              <h3>Resources</h3>
              <p>Consider subscribing to the <a href="https://groups.google.com/d/forum/chrome-debugging-protocol">chrome-debugging-protocol</a> mailing list.
              <p>The <a href="https://github.com/chromedevtools/devtools-protocol">devtools-protocol repo</a> issue tracker can also be used for concerns with the protocol. It also hosts the canonical copy of the json files.
              <p>Useful: <a href="https://developers.google.com/web/updates/2017/04/headless-chrome">Getting Started with Headless Chrome</a> and the <a href="https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md">Headless Chromium readme</a>.
              <p>The <a href="https://github.com/cyrus-and/chrome-remote-interface/">chrome-remote-interface</a> node module is recommended, and its <a href="https://github.com/cyrus-and/chrome-remote-interface/wiki">wiki</a> and <a href="https://github.com/cyrus-and/chrome-remote-interface/issues?utf8=%E2%9C%93&q=is%3Aissue%20">issue tracker</a> are full of useful recipes.
              <p>The <a href="https://github.com/ChromeDevTools/awesome-chrome-devtools#chrome-devtools-protocol">awesome-chrome-devtools</a> page links to many of the tools in the protocol ecosystem, including protocol API libraries in JavaScript, TypeScript, Python, Java, and Go.


              Many applications and libraries already use the protocol.

              <h3 id="remote">Basics: Using DevTools as protocol client</h3>
              <p>The Developer Tools front-end can attach to a remotely running Chrome instance for debugging.

                For this scenario to work, you should start your
                <i>host</i> Chrome instance with the remote-debugging-port
                command line switch:
              </p>

              <pre class="summary">chrome.exe --remote-debugging-port=9222</pre>

              <p>Then you can start a separate <i>client</i> Chrome instance, using a distinct user profile:</p>

              <pre class="summary">chrome.exe --user-data-dir=&lt;some directory&gt;</pre>

              <p>Now you can navigate to the given port from your <i>client</i> and attach to
                any of the discovered tabs for debugging: <a href="http://localhost:9222">http://localhost:9222</a>
              </p>

              <p>You will find the Developer Tools interface identical to the embedded one and
                here is why:
              </p>
              <ul>
                <li>When you navigate your <i>client</i> browser to the remote's Chrome port,
                  Developer Tools front-end is being served from the <i>host</i> Chrome
                  as a Web Application from the Web Server.
                </li>
                <li>It fetches HTML, JavaScript and CSS files over HTTP
                </li>
                <li>Once loaded, Developer Tools establishes a Web Socket connection to its
                  host and starts exchanging JSON messages with it.
                </li>
              </ul>

              <p>In this scenario, you can substitute Developer Tools front-end with your
                own implementation. Instead of navigating to the HTML page at
                http://localhost:9222, your application can discover available
                pages by requesting: <a href="http://localhost:9222/json">http://localhost:9222/json</a> and getting a JSON object with information about inspectable pages along
                with the WebSocket addresses that you could use in order to start
                instrumenting them.
              </p>

              <p>Remote debugging is especially useful when debugging remote
                instances of the browser or attaching to the embedded devices. Blink port
                owners are responsible for exposing debugging connections to the external
                users.
              </p>

              <p>If you need the protocol version for a specific Chrome client, glance at <code>fetchFromChromeRepo</code> from chrome-remote-interface's <a href="https://github.com/cyrus-and/chrome-remote-interface/blob/master/lib/devtools.js"><code>devtools.js</code></a>.

              <h3>Sniffing the protocol</h3>
              <p>This is especially handy to understand how the DevTools frontend makes use of the protocol.
                First, run Chrome with the debugging port open:
              <pre>alias canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"
  canary --remote-debugging-port=9222 http://localhost:9222 http://chromium.org</pre>

                Then, select the Chromium Projects item in the <em>Inspectable Pages</em> list. Now that DevTools is up and fullscreen, open DevTools to inspect it.
                Cmd-R in the new inspector to make the first restart. Now head to Network Panel, filter by Websocket, select the connection and click the Frames tab.
                Now you can easily see the frames of WebSocket activity as you use the first instance of the DevTools.
              </p>

              <figure class="screenshot">
                <a href="images/debugger-protocol-sniffing-full.png" style="text-align: center; display:block;">
                  <div style="background-image: url(images/debugger-protocol-sniffing-full.png)">
                    <img src="images/debugger-protocol-sniffing.png" alt="Sniffing the debugger protocol">
                  </div>
                  Screenshot of sniffing the DevTools protocol with DevTools
                </a>
              </figure>


              <h3 id="extension">DevTools protocol via Chrome extension</h3>
              <p>
                To allow chrome extensions to interact with the protocol, we introduced
                <a href="https://developer.chrome.com/extensions/debugger.html">chrome.debugger</a>
                extension API that exposes this JSON message
                transport interface. As a result, you can not only attach to the remotely
                running Chrome instance, but also instrument it from its own extension.
              </p>

              <p>Chrome Debugger Extension API provides a higher level API where command
                domain, name and body are provided explicitly in the <code>sendCommand</code>
                call. This API hides request ids and handles binding of the request with its
                response, hence allowing <code>sendCommand</code> to report result in the
                callback function call. One can also use this API in combination with the other
                Extension APIs.
              </p>

              <p>If you are developing a Web-based IDE, you should implement an extension that
                exposes debugging capabilities to your page and your IDE will be able to open
                pages with the target application, set breakpoints there, evaluate expressions
                in console, live edit JavaScript and CSS, display live DOM, network interaction
                and any other aspect that Developer Tools is instrumenting today.
              </p>

              <p>Opening embedded Developer Tools will <a href="#simultaneous">terminate</a> the
                remote connection and thus detach the extension.
              </p>

              <h3 id="faq">Frequently Asked Questions</h3>

              <h4 id="how-is-the-protocol-defined">How is the protocol defined?</h4>
              <p>The canonical protocol definitions live in the Chromium source tree: (<a href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/inspector/browser_protocol.json">browser_protocol.json</a> and <a href="https://chromium.googlesource.com/v8/v8/+/master/src/inspector/js_protocol.json">js_protocol.json</a>). They are maintained manually by the DevTools engineering team. These files are mirrored (hourly) on GitHub <a href="https://github.com/ChromeDevTools/devtools-protocol/tree/master/json">in the devtools-protocol repo</a>.</p>
              <p>The declarative protocol definitions are used across tools. Within Chromium, a binding layer is created for the Chrome DevTools to interact with, and separately the protocol is used for <a href="https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md#client_devtools-api">Chrome Headless’s C++ interface</a>.</p>

              <h4 id="whats-the-protocol_externs-file">What’s the protocol_externs file?</h4>
              <p>It’s created via <a href="https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/scripts/build/generate_protocol_externs.py">generate_protocol_externs.py</a> and useful for tools using closure compiler. The TypeScript story is <a href="https://github.com/ChromeDevTools/devtools-protocol/issues/18">here</a>.</p>

              <h4 id="are-the-http-endpoints-documented">Are the HTTP endpoints documented?</h4>
              <p>Not yet. See bugger-daemon’s <a href="https://github.com/buggerjs/bugger-daemon/blob/master/README.md#api">third-party docs</a>. See also the <a href="https://cs.chromium.org/search/?q=f:devtools_http_handler.cc+%22command+%3D%3D+%22&amp;sq=package:chromium&amp;type=cs">endpoints implementation</a> in Chromium. <code>/json/protocol</code> was added in Chrome 60.</p>

              <h4 id="how-do-i-access-the-browser-target">How do I access the browser target?</h4>
              <p>The endpoint is exposed as <code>webSocketDebuggerUrl</code> in <code>/json/version</code>. Note the <code>browser</code> in the URL, rather than <code>page</code>. If Chrome was launched with <code>--remote-debugging-port=0</code> and chose an open port, the browser endpoint is written to both stderr and the <code>DevToolsActivePort</code> file in browser profile folder.</p>


              <h4 id="simultaneous">Does the protocol support multiple simultaneous clients?</h4>
              <p>
                Chrome 63 introduced support for multiple clients. See <a href="https://developers.google.com/web/updates/2017/10/devtools-release-notes#multi-client">this article</a> for details.
              </p>
              <p>
                Upon disconnnection, the outgoing client will receive a <code>detached</code> event.
                For example: <code>{"method":"Inspector.detached","params":{"reason":"replaced_with_devtools"&#125;}</code>.
                View the <a href="https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/chrome/common/extensions/api/debugger.cc&q=file:debugger.cc%20Reason%20ParseReason&sq=package:chromium&type=cs&">enum of
                possible reasons.</a>
                (For reference: the <a href="https://chromiumcodereview.appspot.com/11361034/">original patch</a>).
                After disconnection, some apps have chosen to pause their state and offer a reconnect button.
              </p>
            </div>
          </section>
        </iron-pages>
      </main>
    </div>
  </app-drawer-layout>
  <script>
    (function() {
      const searchButton = document.getElementById('searchButton');
      const searchMenu = document.getElementById('searchMenu');
      const toolbar = document.getElementById('mainToolbar');
      const listbox = document.getElementById('versions').querySelector('paper-listbox');
      const mainSelector = document.getElementById('main-selector');
      const domainContainer = document.getElementById('domainContainer');
      const crDomain = document.querySelector('cr-domain');
      const domainSummary = document.getElementById('domain-summary');
      const mainElement = document.querySelector('main');

      /**
       * Mapping from domain shortcut to its full name. Used for updating the title.
       */
      const domainToNameMapping = {
        '1-2': 'stable (1.2)',
        '1-3': 'stable (1.3)',
        'tot': 'latest (tip-of-tree)',
        'v8': 'v8-inspector (node)',
      }

      const defaultTitle = 'Chrome DevTools Protocol Viewer';

      /**
       * Hide links based on the domain that we are showing. The classList of
       * each link has the (multiple) domains it is documented for.
       * @param  {String} domain The domain that we have currently selected
       */
      function updateDomainSelectorLinks(domain) {
        for (const link of domainContainer.querySelectorAll('a')) {
          if (link.classList.contains(domain)) {
            link.style.display = 'flex';
            link.href = `${domain}/${link.dataset.title}`;
          } else {
            link.style.display = 'none';
          }
        }
      }

      let domainSelectionPromise;

      async function updateLocationBindings(newLocation) {
        // 18 is length of "/devtools-protocol"
        let newPage = newLocation.startsWith('/devtools-protocol') ? newLocation.substring(18) : newLocation;
        // Cut off start slash if it exists
        if (newPage.startsWith('/')) {
          newPage = newPage.substring(1);
        }
        let index, newVersion = newPage;
        if ((index = newVersion.indexOf('/')) !== -1) {
          newVersion = newVersion.substring(0, index);
        }
        listbox.selected = newVersion;

        if (!domainToNameMapping[newVersion]) {
          document.title = defaultTitle;
          mainSelector.selected = newVersion;
          newVersion = 'tot';
        } else if (index === -1 || !newVersion.substring(index)) {
          document.title = `${defaultTitle} - ${domainToNameMapping[newVersion]}`;

          // Show the summary page if no sub-category was selected
          mainSelector.selected = 'domain-summary';
          fetch(`_versions/${newVersion}.html`)
            .then(result => result.text())
            .then((content) => domainSummary.innerHTML = `<h2 class="heading">${domainToNameMapping[newVersion]}</h2>${content}`);
        }

        updateDomainSelectorLinks(newVersion);
        searchButton.protocolSrc = `search_index/${newVersion}.json`;

        if (index !== -1 && newPage.substring(index) !== '/') {
          let newDomain = newPage.substring(index + 1);
          if (newDomain.endsWith('/')) {
            newDomain = newDomain.substring(0, newDomain.length - 1);
          }

          document.title = `${defaultTitle} - ${newDomain}`;

          // If we are switching between domains, but do not select a sub-category
          // scroll all the way to the top again.
          if (!location.hash) {
            mainElement.scrollTop = '0';
          }

          domainSelectionPromise = new Promise(async (resolve) => {
            mainSelector.selected = 'domain';

            const result = await fetch('_data/' + newVersion + '/protocol.json');
            const versionData = await result.json();

            // confirm we have the dom elements present in the nav
            const hardcodedDomains = Array.from(domainContainer.children).map(e => e.dataset.title);
            versionData.domains.forEach(d => {
              if (!hardcodedDomains.includes(d.domain)) console.error(`${d.domain} not present in domainContainer`);
            });

            crDomain.domain = versionData.domains.find(d => d.domain === newDomain);
            domainContainer.selected = newDomain;
            crDomain.version = newVersion;

            // Wait for one frame and then resolve this promise, to make sure
            // all data and elements are rendered, such that we can correctly
            // scroll into view.
            window.requestAnimationFrame(() => {
              resolve();
              domainSelectionPromise = undefined;
            });
          });
        } else {
          // We have not selected any domain, so we have to show the summar at the top
          mainElement.scrollTop = '0';
        }
      }

      const domainElement = document.querySelector('cr-domain');

      function processHashUpdate() {
        function scrollIntoView() {
          for (const details of domainElement.shadowRoot.querySelectorAll('cr-domain-details')) {
            if (`${details.type}-${details.name}` === location.hash) {
              details.scrollIntoView({behavior: 'instant', block: 'start'});
              return;
            }
          }

          // After the hash update, we have not found any match.
          // Could be the case that the hash is empty,
          // or there is simply no match. Anyways, scroll all the way to the top
          mainElement.scrollTop = '0';
        }
        window.requestAnimationFrame(() => {
          // If we are still switching domains and fetching data (which is async)
          // have to wait for that promise to resolve. Only then we have rendered
          // data and can scroll to the correct element in the view
          if (domainSelectionPromise) {
            domainSelectionPromise.then(scrollIntoView);
          } else {
            scrollIntoView();
          }
        });
      }

      searchButton.resultsMenu = searchMenu;

      let lastSelected;
      searchButton.addEventListener('active', () => {
        lastSelected = mainSelector.selected;
        mainSelector.selected = 'search';
      });

      searchButton.addEventListener('inactive', () => {
        mainSelector.selected = lastSelected;
      });

      searchMenu.addEventListener('item-activate', () => {
        searchButton.handleItemActivate_();
      });

      const location = document.getElementById('location');
      location.addEventListener('path-changed', (e) => {
        searchButton.inputActive = false;
        updateLocationBindings(e.detail.value);
      });

      location.addEventListener('hash-changed', (e) => {
        processHashUpdate();
      });


      window.addEventListener('load', function() {
        customElements.whenDefined('iron-location').then(() => {
          updateLocationBindings(location.path);
          processHashUpdate();
        });
        searchButton.addEventListener('active', function() {
          toolbar.classList.add('search-active');
        }, false);
        searchButton.addEventListener('inactive', function() {
          toolbar.classList.remove('search-active');
        }, false);
      });

      if ('serviceWorker' in window.navigator) {
        navigator.serviceWorker.register('service-worker.js');
      }
    })();
  </script>
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-60854461-3', 'auto');
    ga('send', 'pageview');

  </script>
</body>
</html>
